<?php
namespace TwinPeaks\Core;

require __DIR__ . '/CApplication.php';
require __DIR__ . '/interface/IUser.php';

const ERR_PAGE_NOT_FOUND = 1000;
const ERR_SECURITY_VIOLATION = 1001;

const ORDER_ALLOW_DENY = 1;
const ORDER_DENY_ALLOW = 2;

abstract class CWebApplication extends CApplication
{	
	/**
	 * @var CUri
	 */
	static private $requestURI;

	/**
	 * @var CHttpRequest
	 */
	private $httpRequest;
	
	/**
	 * @var CHttpResponse
	 */
	private $httpResponse;
	
	/**
	 * @var CFrontController
	 */
	private $fcInstance;
	
	/**
	 * @var CRule
	 */
	private $ruleInstance;
	
	/**
	 * If set to TRUE no response body will be returned
	 * 
	 * @var boolean
	 */
	private $noResponseBody = false;

	/**
	 * @return CFrontController|null
	 */
	abstract function selectFrontController($requestURI);

	/**
	 * @return void
	 */
	static function handleRequest($reqURI = null)	
	{
		if ($reqURI === null)
		{
			$reqURI = new Http\CUri();
			$reqURI->setScheme(isset($_SERVER['HTTPS']) ?
				Http\HTTPS_PROTOCOL_TOKEN : Http\HTTP_PROTOCOL_TOKEN);
			$reqURI->setHost($_SERVER['SERVER_NAME']);
			$reqURI->setPath($_SERVER['REQUEST_URI']);

			$URI_Path = $_SERVER['REQUEST_URI'];
			if (($pos = strpos($URI_Path, '?')) !== false)
				$reqURI->setPath(substr($URI_Path, 0, $pos - 1));
			else
				$reqURI->setPath($URI_Path);

			$reqURI->setQuery($_SERVER['QUERY_STRING']);
			$matches = null;
			if (preg_match('/#(.*)$/', $_SERVER['QUERY_STRING'], $matches))
				$reqURI->setFragment($matches[1]);
		}
		elseif (is_string($reqURI))
		{
			$reqURI = Http\CUri::createFromString($reqURI);
		}

		if (!($reqURI instanceof Http\CUri) ||  !$reqURI->isValid() ||
			!$reqURI->isAbsolute())
		{
			exit;
		}
	
		static::alterRequestURI($reqURI);
		self::$requestURI = $reqURI;	
		
		$appInstance = static::getInstance();
		$config = $appInstance->getConfig();
		
		// If set mock the entiry page
		if (isset($_GET['_mock_page_all']))
			$config->addVariable('_mock_page_all', true);
		// If set mock particular template whose filename is specified by the variable
		else if (isset($_GET['_mock_page']))
			$config->addVariable('_mock_page', $_GET['_mock_page']);
	
		return $appInstance;
	}
	
	/**
	 * @return CLayout|true
	 */
	function createPageLayout()
	{
		return false;
	}

	/**
	 * @return void
	 */
	protected function handleSysException($excep)
	{
		$this->handleException($excep, 'onSysException');
	}

	/**
	 * @return void
	 */
	protected function handleCommonException($excep)
	{
		if ($excep->getCode() == CCommonException::NO_FC_MATCHED ||
			$excep->getCode() == CCommonException::FC_NO_RULE_MATCHED)
		{
			cascade_invoc($this->getReverseCascade(), 'onPageNotFound', [$excep]);
			$this->getResponse()->dispatch();
		}
		else
			$this->handleException($excep, 'onCommonException');
	}
	
	/**
	 * Handles user defined exceptions
	 * 
	 * @return void
	 */
	protected function handleCustomException($excep)
	{
		$this->handleException($excep, 'onException');
	}
	
	/**
	 * @return void
	 */
	protected function handlePDOException($excep)
	{
		$this->handleException($excep, 'onPDOException');
	}

	/**
	 * @return void
	 */
	protected function handleException($excep, $handlerName)
	{
		cascade_invoc($this->getReverseCascade(), $handlerName, [$excep]);
        $this->getResponse()->dispatch();		
	}
	
	
	/**
	 * @return CWebApplication
	 */
	function initialize()
	{
		$this->httpRequest = Http\CRequest::getCurrent();
		$this->setConfig();	
		$this->init();
		return $this;
	}
	
	/**
	 * @return mixed
	 */
	protected function runAction($execFlag = true)
	{
		$reqURI = $this->getRequestURI();
		$this->fcInstance = $this->selectFrontController($reqURI);
		if (!$this->fcInstance)
			CCommonException::noFrontController($reqURI)->_throw();			

		$this->fcInstance->onSelect();

		// Select a rule to execute.
		$this->ruleInstance = $this->fcInstance->getRuleByURI($reqURI);		
		if (!$this->ruleInstance)
		{
			// If no rule has been found ::onNoRuleMatched method may be used
			// to specify a rule.
			$rule = cascade_invoc([$this->fcInstance, $this], 'onNoRuleMatched');
			if ($rule instanceof CRule)
				$this->ruleInstance = $rule;
			else
				CCommonException::noRule($this->fcInstance, $reqURI)->_throw();
		}

		if (!$this->ruleInstance->isMethodAllowed($this->getRequest()->getMethodType()))
			CCommonException::httpRequestMethodNotAllowed()->_throw();

		if ($execFlag)
			$this->ruleInstance->execute();
		
		return $this->ruleInstance->getExecutionResult();
	}
	
	/**
	 * @return null|string
	 */
	function run()
	{
		try
		{
			$this->initialize();
			$config = $this->getConfig();
			
			$result = $this->runAction(($config->_mock_page_all || $config->_mock_page) ? false : true);
			
			if ($this->getOption(OPT_RETURN_CONTROLLER_RESULT))
				return $result;			

			if (($layout = $this->getPageLayout()) != null)
			{
				$layout->bindData($result);
				$resBody = $layout->render();
			}
			else
				$resBody = $result;

			$this->getResponse()
				->setBody($resBody)
				->dispatch();
		}
        catch (CCommonException $excep)
		{
			$this->handleCommonException($excep);
		}
		catch (CSysException $excep)
		{
			$this->handleSysException($excep);
		}
		catch (\Exception $excep)
		{
			if ($excep instanceof CBaseException)
				$this->handleCustomException($excep);
			elseif (($pdoExcep = self::findException($excep, 'PDOException')) !== null)
			{
				$this->handlePDOException($excep);
			}
		}
		
		return $this;
	}
	
	/**
	 * Adds trailing slash to the URI path
	 * 
	 * @return null
	 */
	static protected function alterRequestURI($uri)
	{
		$newPath = rtrim($uri->getPath(), '/') . '/';
		$uri->setPath($newPath);
	}
	
	/**
	 * @return CFrontController
	 */
	final function getFrontController()
	{
		return $this->fcInstance;
	}
	
	/**
	 * @return CRule
	 */
	final function getCurrentRule()
	{
		return $this->ruleInstance;
	}

	/**
	 * @return CRequest
	 */
	final function getRequest()
	{
		return $this->httpRequest;
	}
    
    /**
     * @return CResponse
     */
    final function getResponse()
    {
        return $this->httpRequest->getResponse();
    }
	
	/**
	 * @return CUri
	 */
	final function getRequestURI()
	{
		return self::$requestURI;
	}
	
	/**
	 * @return void
	 */
	function checkUserRights($rule)
	{
		$sess = $this->getSession();
		
		if (!$sess->isActive() && $rule->isAuthReq())
			CException::accessDenied($rule)
				->setType(CException::AUTH_REQUIRED)
				->_throw()
			;
		
		if (!$sess->isActive())
			return false;
		
		return true; // Keep at checking user rights
	}
	
	/**
	 * @return boolean
	 */
	final function isCommandLine()
	{
		return PHP_SAPI == 'cli';
	}
	
	/**
	 * @return void
	 */
	final protected function onCreate()
	{
		parent::onCreate();
	}
	
	/**
	 * @return void
	 */
	protected function init()
	{
		// ...
	}
	
	/**
	 * @return void
	 */
	function onNoRuleMatched()
	{
		$this->getResponse()
			->setStatusCode(Http\RES_NOT_FOUND)
			->dispatch();
		exit;
	}
	
	
	/**
	 * @return void
	 */
	function errPageNotFound()
	{
		$this->getResponse()->setStatusCode(404);
	}
	
	protected function errAuthRequired()
	{
		
	}
	
	/**
	 * 
	 */
	function getSession()
	{
		return \TwinPeaks\Stdlib\CSession::getInstance();
	}
	
	/**
	 * @return integer
	 */
	final function getRequestType()
	{
		switch ($_SERVER['REQUEST_METHOD'])
		{
			case 'GET':
				return Http\GET_METHOD;
			case 'POST':
				return Http\POST_METHOD;
			case 'HEAD':
				return Http\HEAD_METHOD;
			case 'PUT':
				return Http\PUT_METHOD;
			case 'DELETE':
				return Http\DELETE_METHOD;
		}
	}

	/**
	 * @return array
	 */
	final function getCascade()
	{
		$cascade = [$this];
		if ($this->fcInstance)
			$cascade[] = $this->fcInstance;
		if ($this->ruleInstance && ($ctrlInstance = $this->ruleInstance->getController()))
			$cascade[] = $ctrlInstance;

		return $cascade;
	}
	
	/**
	 * @return array
	 */
	final function getReverseCascade()
	{
		return array_reverse($this->getCascade());
	}
	
	/**
	 * @return void
	 */
	function onPageNotFound()
	{
		$this->getResponse()->setStatusCode(Http\STATUS_CODE_NOT_FOUND)
			->dispatch();
	}
	
	/**
	 * @return CLayout|null
	 */
	function getPageLayout()
	{
		static $pageLayout;
		
		if (isset($pageLayout))
			return $pageLayout;

		$pageLayout = cascade_invoc($this->getReverseCascade(), 'createPageLayout');

		return $pageLayout;
	}
}
